package com.gitee.wsl.logger.printer

import com.gitee.wsl.ext.base.currentDateTime
import com.gitee.wsl.logger.Logger
import com.gitee.wsl.logger.LogLevel
import com.gitee.wsl.logger.LogPrinter
import com.gitee.wsl.logger.context.EmptyLogContext
import com.gitee.wsl.logger.context.LogContext
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.datetime.LocalDateTime
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.channels.onFailure
import kotlinx.coroutines.launch

class AsyncLogPrinter(
    val base:LogPrinter,
    queueCapacity: Int = Channel.UNLIMITED,
    context: CoroutineContext = Dispatchers.Default
) : CoroutineScope,LogPrinter {

    override val coroutineContext: CoroutineContext = CoroutineName("Logger-AsyncLogPrinter")

    private val queue = Channel<Log>(queueCapacity)

    init {
        launch(context) {
            for (log in queue) {
                base.log(log.level,log.loggerName,log.message,log.throwable)
            }
        }
    }

    override fun log(level: LogLevel, tag: String, messageAny: Any?, throwable: Throwable?) {
        val log = LogData(tag,level,messageAny)
        queue.trySend(log).onFailure {
            launch { queue.send(log) }
        }
    }

}

fun Logger.toAsync(queueCapacity: Int = Channel.UNLIMITED, context: CoroutineContext = Dispatchers.Default){
    if(this.logPrinter is AsyncLogPrinter) return
    logPrinter = AsyncLogPrinter(logPrinter,queueCapacity,context)
}

fun LogPrinter.toAsync(queueCapacity: Int = Channel.UNLIMITED,
                       context: CoroutineContext = Dispatchers.Default
) = AsyncLogPrinter(this,queueCapacity,context)

internal interface Log {
    val loggerName: String
    val level: LogLevel
    val message: Any?
    val throwable: Throwable?
    val meta: Map<String, Any?>
    val context: LogContext
    val threadName: String?
    val timestamp: LocalDateTime
}

internal data class LogData(
    override val loggerName: String,
    override val level: LogLevel,
    override val message: Any?,
    override val throwable: Throwable? = null,
    override val meta: Map<String, Any?> = emptyMap(),
    override val context: LogContext = EmptyLogContext,
    override val threadName: String? = null,
    override val timestamp: LocalDateTime = currentDateTime,
) : Log